#pragma once

#include "Math.h"
#include "Attribute.h"

class Matrix
{
public:
	struct MatrixData
	{
	public:
		static MatrixData Identity()
		{
			static MatrixData identity;
			return identity;
		};

		MatrixData()
		{
			m11 = 1.0f;		m12 = .0f;
			m21 = .0f;		m22 = 1.0f;
			dx = .0f;		dy = .0f;
		}

		MatrixData& operator=(const MatrixData& other)
		{
			if (this == &other)
				return *this;

			m11 = other.m11;		m12 = other.m12;
			m21 = other.m21;		m22 = other.m22;
			dx = other.dx;			dy = other.dy;

			return *this;
		}
		bool operator==(const MatrixData& other) const
		{
			return Math::Equal(m11,other.m11)
				&& Math::Equal(m12,other.m12)
				&& Math::Equal(m21,other.m21)
				&& Math::Equal(m22,other.m22)
				&& Math::Equal(dx,other.dx)
				&& Math::Equal(dy,other.dy);
		}
		MatrixData& operator*=(const MatrixData& other)
		{
			MatrixData newMatrixdata;
			newMatrixdata.m11 = m11 * other.m11 + m12 * other.m21;
			newMatrixdata.m12 = m11 * other.m12 + m12 * other.m22;	
			newMatrixdata.m21 = m21 * other.m11 + m22 * other.m21;
			newMatrixdata.m22 = m21 * other.m21 + m22 * other.m22;	
			newMatrixdata.dx = dx * other.m11 + dy * other.m21 + other.dx;
			newMatrixdata.dy = dx * other.m12 + dy * other.m22 + other.dy;
			*this = newMatrixdata;

			return *this;
		}
		float m11;float m12;
		float m21;float m22;
		float dx;float dy;
	};

	enum matrixOrder
	{
		prependOrder    = 0,
		appendOrder     = 1
	};

	static Matrix Identity()
	{
		static Matrix matrix;
		return matrix;
	}

	Matrix(){}
	Matrix(float m11,float m12,float m21,float m22,float dx,float dy)
	{
		m_matrixData.m11 = m11;		m_matrixData.m12 = m12;
		m_matrixData.m21 = m21;		m_matrixData.m22 = m22;
		m_matrixData.dx = dx;		m_matrixData.dy = dy;
	}
	void Reset()
	{
		m_matrixData = MatrixData::Identity();
	}
	float OffsetX() const{return m_matrixData.dx;}
	float OffsetY() const{return m_matrixData.dy;}
	bool IsIdentity() const
	{
		return m_matrixData==MatrixData::Identity();
	}
	Matrix& Multiply(const Matrix& other, matrixOrder order=prependOrder)
	{
		ASSERT(this != &other);

		if(order == appendOrder)
		{
			m_matrixData *= other.m_matrixData;
		}
		else if(order == prependOrder)
		{
			MatrixData data = other.m_matrixData;
			data *= m_matrixData;
			m_matrixData = data;
		}

		return *this;
	}
	Matrix& Translate(float dx, float dy, matrixOrder order=prependOrder)
	{
		Matrix identity;
		identity.m_matrixData.dx = dx;
		identity.m_matrixData.dy = dy;
		return Multiply(identity,order);
	}
	Matrix& Scale(float scaleX, float scaleY, matrixOrder order=prependOrder)
	{
		Matrix identity;
		identity.m_matrixData.m11 = scaleX;
		identity.m_matrixData.m22 = scaleY;
		return Multiply(identity,order);
	}
	Matrix& Rotate(float angle,matrixOrder order=prependOrder)
	{
		float value = angle/180.0f * Math::PI;
		Matrix identity;
		identity.m_matrixData.m11 = cos(value);	identity.m_matrixData.m12 = -sin(value);
		identity.m_matrixData.m21 = sin(value);	identity.m_matrixData.m22 = cos(value);
		return Multiply(identity,order);
	}
	Matrix& RotateAt(float angle, const Point& center,matrixOrder order=prependOrder)
	{
		Matrix identity;
		identity.Translate(-center.x,-center.y,appendOrder)
				.Rotate(angle,appendOrder)
				.Translate(center.x,center.y,appendOrder);
		return Multiply(identity,order);
	}
	Matrix& Shear(float shearX, float shearY,matrixOrder order=prependOrder)
	{
		Matrix identity;
		identity.m_matrixData.m12 = shearX;
		identity.m_matrixData.m21 = shearY;
		return Multiply(identity,order);
	}
	void GetElements(float& m11,float& m12,float& m21,float& m22,float& dx,float& dy) const
	{
		m11 = m_matrixData.m11;	m12 = m_matrixData.m12;
		m21 = m_matrixData.m21;	m22 = m_matrixData.m22;
		dx	= m_matrixData.dx;	dy = m_matrixData.dy;
	}
private:
	MatrixData m_matrixData;
};